package QC.MessageBox.handler;

import QC.MessageBox.annotation.MethodLog;
import QC.MessageBox.annotation.RouteMapping;
import QC.MessageBox.beans.*;
import QC.MessageBox.beans.appmsg.ExtraMsg;
import QC.MessageBox.beans.appmsg.ImageInfo;
import QC.MessageBox.beans.dto.UserActionDTO;
import QC.MessageBox.beans.rcform.RCSingleForm;
import QC.MessageBox.beans.wxmsg.WXEncryptMsg;
import QC.MessageBox.beans.wxmsg.eventmsg.WXCustoMenuMsg;
import QC.MessageBox.beans.wxmsg.eventmsg.WXQRCodeMsg;
import QC.MessageBox.beans.wxmsg.eventmsg.WXSubcribe;
import QC.MessageBox.beans.wxmsg.normalmsg.WXImageMsg;
import QC.MessageBox.beans.wxmsg.normalmsg.WXTextMsg;
import QC.MessageBox.beans.wxmsg.passivemsg.WXArticleReply;
import QC.MessageBox.common.*;
import QC.MessageBox.enums.*;
import QC.MessageBox.service.*;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
/**
 * Created by Andaren on 2017/5/9.
 * Used for: 处理微信请求
 */
@RouteMapping(path = "/wx")
public class WXHandler extends HandlerBase {

    private static final Logger LOGGER = LoggerFactory.getLogger(WXHandler.class);

    private BlockingService blockingService;

    public WXHandler () {}

    public WXHandler (Map<String, Object> handlerMap) {
        Objects.requireNonNull(handlerMap);
        this.dataAccess = (DataAccess)handlerMap.get("dataAccess");
        this.httpService = (HttpService)handlerMap.get("httpService");
        this.sqlService = (MySQLService)handlerMap.get("mySQLService");
        this.redisService = (RedisService)handlerMap.get("redisService");
//        this.mongoService = (MongoService) handlerMap.get("mongoService");
        this.webClientService = (WebClientService)handlerMap.get("webClientService");
        this.blockingService = (BlockingService)handlerMap.get("blockingService");
    }

    /**
     * 分发处理微信消息
     * @param rt
     */
    @RouteMapping(path = "/message", method = HttpMethod.POST)
    @MethodLog("处理微信消息")
    public void handleMsgFromWx(RoutingContext rt) {
        LOGGER.info("未经任何处理的微信消息->：\n" + rt.getBodyAsString());
        Future<Void> wxHandlerFut = Future.future();

        HttpServerRequest request = rt.request();
        HttpServerResponse response = rt.response();
        // 日常返回
        response.end(WXMessage.MESSAGE_SUCCESS);
        // 构造微信消息
        WXEncryptMsg encryptMsg = new WXEncryptMsg(rt.getBodyAsString(), request).init();
        if (encryptMsg.getEncrypted()) {
            LOGGER.info("解密后的微信消息->：\n{}", encryptMsg.getSrcXmlStr());
        }

        // 获取公众号信息
        dataAccess.getWXPublicInfoByDeveloperId(encryptMsg.getToUserName())
                .compose(pubInfo -> {
                    // 处理加密消息
                    handleEncryptMsgBeforeStart(encryptMsg, pubInfo.getAppId());

                    WXPublic wxPublic = pubInfo;
                    String msgType = String.valueOf(encryptMsg.getElementMap().get("MsgType"));
                    // 处理结果
                    Future<Void> handleResultFuture = Future.future();
                    // 消息细分
                    try {
                        switch (msgType) {
                            case Constants.WX_MSG_TYPE_TEXT :
                                handleResultFuture = handleTextMsg(encryptMsg, wxPublic);
                                break;
                            case Constants.WX_MSG_TYPE_IMAGE :
                                handleResultFuture = handleImageMsg(encryptMsg, wxPublic);
                                break;
                            case Constants.WX_MSG_TYPE_EVENT :
                                String event = String.valueOf(encryptMsg.getElementMap().get("Event"));
                                // 获取微信access_token
                                getAccessTokenIfExpired(wxPublic.getDeveloperId())
                                        .compose(tokenAr -> {
                                            Future<Void> eventHandlerFuture = Future.future();
                                            wxPublic.setAccessToken(tokenAr);
                                            try {
                                                if (Constants.WX_MSG_TYPE_EVENT_SUBSCRIBE.equals(event)) {
                                                    String ticket = String.valueOf(encryptMsg.getElementMap().get("Ticket"));
                                                    if (!"null".equals(ticket)) {
                                                        // 扫描带参数二维码事件
                                                        LOGGER.info("扫描带参数二维码事件");
                                                        eventHandlerFuture = handleQrSubcribeMsg(encryptMsg, wxPublic);
                                                    } else {
                                                        // 关注事件
                                                        LOGGER.info("关注事件");
                                                        eventHandlerFuture = handleSubcribeMsg(encryptMsg, wxPublic);
                                                    }
                                                } else if (Constants.WX_MSG_TYPE_EVENT_UNSUBSCRIBE.equals(event)) {
                                                    // 取消关注事件
                                                    eventHandlerFuture = handleUnSubcribeMsg(encryptMsg, wxPublic);
                                                } else if (Constants.WX_MSG_TYPE_EVENT_CLICK.equals(event)) {
                                                    eventHandlerFuture = handleClickMenuMsg(encryptMsg, wxPublic);
                                                } else if (Constants.WX_MSG_TYPE_EVENT_VIEW.equals(event)) {
                                                    eventHandlerFuture = handleViewMenuMsg(encryptMsg, wxPublic);
                                                } else {
                                                    eventHandlerFuture.fail("[不能处理的消息类型]");
                                                }
                                            } catch (Exception eventExcep) {
                                                eventHandlerFuture.fail(eventExcep.getCause());
                                                eventExcep.printStackTrace();
                                            }
                                            return eventHandlerFuture;
                                        }).setHandler(handleResultFuture.completer());
                                break;
                            default:
                                handleResultFuture.fail("[不能处理的消息类型]");
                                break;
                        }
                    } catch (Exception exp) {
                        LOGGER.error("[消息处理过程中出现异常error={}]", exp.getMessage(), exp.getCause());
                        JsonObject logJson = new JsonObject()
                                .put("_id", PeriodicTime.getCurrentTimeByyyyMMddHHmmssSSS())
                                .put("encryptMsg", Json.encode(encryptMsg))
                                .put("error", exp.getMessage());
//                        mongoService.insert("wx_msg_handler_error", logJson);
                        handleResultFuture.fail(exp.getMessage());
                    }
                    return handleResultFuture;
                }).setHandler(wxHandlerFut.completer());
        // 处理完请求后
        wxHandlerFut.setHandler(handleAr -> {
            if (handleAr.failed()) {
                JsonObject logJson = new JsonObject()
                        .put("_id", PeriodicTime.getCurrentTimeByyyyMMddHHmmssSSS())
                        .put("encryptMsg", Json.encode(encryptMsg))
                        .put("error", handleAr.cause().getMessage());
//                mongoService.insert("wx_msg_handler_failed", logJson);
                LOGGER.error("[微信消息处理失败：error= {}]", handleAr.cause().getMessage(), handleAr.cause());
            } else {
                LOGGER.info("[微信消息处理完成]");
            }
        });
    }

    /**
     * 根据消息加密类型处理消息
     * @param encryptMsg
     * @param appId
     */
    public void handleEncryptMsgBeforeStart(WXEncryptMsg encryptMsg, String appId) {
        if (encryptMsg.getEncrypted()) {
            encryptMsg.decryptWXMsg(appId);
        } else {
            encryptMsg.setDecrypt(encryptMsg.getEncrypt());
        }
        // 过滤表情
        encryptMsg.filterWXMsg()
                .reSetElementMap();
    }

    /**
     * 处理文本消息
     * @param encryptMsg
     * @param wxPublic
     * @return
     * @throws Exception
     */
    public Future<Void> handleTextMsg(WXEncryptMsg encryptMsg, WXPublic wxPublic) throws Exception {
        Future<Void> textMsgHandlerFut = Future.future();
        LOGGER.info(">>>:text-msg");
        WXTextMsg textMsg = (WXTextMsg) XMLUtils.mapToBean(encryptMsg.getElementMap(), WXTextMsg.class);
        // 获取APP用户对应的talkId
        Future<AppUser> appUserTalkIdFut = getMappedAppUserInfo(textMsg.getFromUserName(), textMsg.getToUserName());
        // 获取随机app客服
        Future<CustomerServ> customerServFut = dataAccess.getCSInfoByRandom();
        // 获取用户信息
        Future<WXUser> wxUserFuture = dataAccess.findWxUserByOpenId(textMsg.getFromUserName());//Future.succeededFuture(new WXUser());//

        CompositeFuture.all(appUserTalkIdFut, customerServFut, wxUserFuture).setHandler(comAs -> {
            if (comAs.failed()) {
                textMsgHandlerFut.fail(comAs.cause());
            } else {
                AppUser appUser = comAs.result().resultAt(0);
                CustomerServ customerServ = comAs.result().resultAt(1);
                WXUser wxUser = comAs.result().resultAt(2);
                // 生成消息数据
                ExtraMsg extraMsg = new ExtraMsg();
                extraMsg.setDeveloperId(textMsg.getToUserName())
                        .setHeadUrl(wxUser.getHeadUrl() == null ? "" : wxUser.getHeadUrl())
                        .setNickName(wxUser.getWxNickName() == null ? "" : wxUser.getWxNickName())
                        .setPublicName(wxPublic.getName() == null ? "" : wxPublic.getName())
                        .setOpenId(textMsg.getFromUserName())
                        .setType(SourceType.WX.val())
                        .setChannelType(ChannelType.WX.val())
                        .setTimestamp(textMsg.getCreateTime());
                JsonObject msgJson = new JsonObject();
                msgJson.put("content", textMsg.getContent())
                        .put("extra", Json.encode(extraMsg));

                RCSingleForm singleForm = new RCSingleForm();
                singleForm.setFromUserId(customerServ.getTalkId())
                        .setToUserId(appUser.getTalkId())
                        .setObjectName(RCMsgType.TEXT.val())
                        .setContent(msgJson.encodePrettily());
                // 记录消息
                MsgRecord msgRecord = new MsgRecord();
                msgRecord.setOpenId(textMsg.getFromUserName())
                        .setMsgType(textMsg.getMsgType())
                        .setSourceType(MsgRecord.MSG_FROM_WX)
                        .setContent(msgJson.toString())
                        .setCsId(appUser.getCsId())
                        .setCsNickName(appUser.getNickname())
                        .dropEmoji()
                        .setIsRead("0")
                        .setSendtime(PeriodicTime.getCurrentTimestamp());

                // 保存消息记录
                sqlService.save(msgRecord)
                        .compose(newMsgRecord -> {
                            // 更新互动时间
                            return sqlService.updateUserLimit(textMsg.getFromUserName(), new EnumMap<ActiveLimit, Long>(ActiveLimit.class){
                                {
                                    put(ActiveLimit.ACTIVE_LIMIT, PeriodicTime.getCurrentTimestamp());
                                    put(ActiveLimit.LATELY_MSG_ID, newMsgRecord.getId());
                                }
                            });
                        })
                        .compose( (Void) -> {
                            // 发送消息至融云平台
                            sendSingMsgToRC(singleForm, textMsgHandlerFut);
                        }, textMsgHandlerFut);
            }
        });
        return textMsgHandlerFut;
    }

    /**
     * 处理图片消息
     * @param encryptMsg
     * @param wxPublic
     * @return
     * @throws Exception
     */
    public Future<Void> handleImageMsg(WXEncryptMsg encryptMsg, WXPublic wxPublic) throws Exception {
        Future<Void> imageMsgHandlerFut = Future.future();
        LOGGER.info(">>>:image-msg");
        WXImageMsg wxImageMsg = (WXImageMsg)XMLUtils.mapToBean(encryptMsg.getElementMap(), WXImageMsg.class);
        // 获取APP用户对应的talkId
        Future<AppUser> appUserTalkIdImageFut = getMappedAppUserInfo(wxImageMsg.getFromUserName(), wxImageMsg.getToUserName());
        // 获取随机app客服
        Future<CustomerServ>  customerServImageFut = dataAccess.getCSInfoByRandom();
        // 获取用户信息
        Future<WXUser> wxUserImageFuture = dataAccess.findWxUserByOpenId(wxImageMsg.getFromUserName());
        // 获取图片信息
        Future<ImageInfo> imageInfoFuture = blockingService.getImageInfoByUrl(wxImageMsg.getPicUrl());
        // 测试，获取所有客服TalkId列表
//                            Future<List<String>> getAllCsTalkIdImageFut = httpService.getAllCSTalkIdList(wxImageMsg.getToUserName());
//                            CompositeFuture.all(appUserTalkIdImageFut, customerServImageFut, wxUserImageFuture, getAllCsTalkIdImageFut).setHandler(comAs -> {
        CompositeFuture.all(appUserTalkIdImageFut, customerServImageFut, wxUserImageFuture, imageInfoFuture).setHandler(comAs -> {
            if (comAs.failed()) {
                imageMsgHandlerFut.fail(comAs.cause());
            } else {
                AppUser appUser = comAs.result().resultAt(0);
                CustomerServ customerServ = comAs.result().resultAt(1);
                WXUser wxUser = comAs.result().resultAt(2);
                ImageInfo imageInfo = comAs.result().resultAt(3);
//                                    List<String> talkIdList = comAs.result().resultAt(3);
                // 生成消息数据
                ExtraMsg extraMsg = new ExtraMsg();
                extraMsg.setDeveloperId(wxImageMsg.getToUserName())
                        .setHeadUrl(wxUser.getHeadUrl() == null ? "" : wxUser.getHeadUrl())
                        .setNickName(wxUser.getWxNickName() == null ? "" : wxUser.getWxNickName())
                        .setPublicName(wxPublic.getName() == null ? "" : wxPublic.getName())
                        .setOpenId(wxImageMsg.getFromUserName())
                        .setType(SourceType.WX.val())
                        .setChannelType(ChannelType.WX.val())
                        .setTimestamp(wxImageMsg.getCreateTime());
//                                    for (String talkId : talkIdList) {
                JsonObject msgJson = new JsonObject();
                // TODO content 表示图片缩略图，格式为 JPG，大小不超过 30k，注意在 Base64 进行 Encode 后需要将所有 \r\n 和 \r 和 \n 替换成空，imageUri 为图片 Url
                msgJson.put("content", imageInfo.toJson().encode())
                        .put("imageUri", wxImageMsg.getPicUrl())
                        .put("extra", Json.encode(extraMsg));

                RCSingleForm singleForm = new RCSingleForm();
                singleForm.setFromUserId(customerServ.getTalkId())
                        // .setToUserId(appUserTalkId)
                        .setToUserId(appUser.getTalkId())
                        .setObjectName(RCMsgType.IMAGE.val())
                        .setContent(msgJson.encodePrettily());

                // 记录消息
                MsgRecord msgRecord = new MsgRecord();
                msgRecord.setOpenId(wxImageMsg.getFromUserName())
                        .setMsgType(wxImageMsg.getMsgType())
                        .setSourceType(MsgRecord.MSG_FROM_WX)
                        .setContent(msgJson.toString())
                        .setCsId(appUser.getCsId())
                        .setCsNickName(appUser.getNickname())
                        .dropEmoji()
                        .setIsRead("0")
                        .setSendtime(PeriodicTime.getCurrentTimestamp());

                // 保存消息记录
                sqlService.save(msgRecord)
                        .compose(newMsgRecord -> {
                            // 更新互动时间
                            return sqlService.updateUserLimit(wxImageMsg.getFromUserName(), new EnumMap<ActiveLimit, Long>(ActiveLimit.class){
                                {
                                    put(ActiveLimit.ACTIVE_LIMIT, PeriodicTime.getCurrentTimestamp());
                                    put(ActiveLimit.LATELY_MSG_ID, newMsgRecord.getId());
                                }
                            });
                        })
                        .compose( (Void) -> {
                            // 发送消息至融云平台
                            sendSingMsgToRC(singleForm, imageMsgHandlerFut);
                        }, imageMsgHandlerFut);
            }
//                                }
        });
        return imageMsgHandlerFut;
    }

    /**
     * 发送消息至融云平台
     * @param singleForm
     * @param fut
     */
    public void sendSingMsgToRC(RCSingleForm singleForm, Future<Void> fut) {
        // 发送消息至融云平台
        httpService.postSingleMsg(singleForm).setHandler(postAs -> {
            if (postAs.succeeded()) {
                fut.complete();
            } else {
                LOGGER.info("融云接口发送消息失败：" + postAs.cause().getMessage());
                fut.fail(postAs.cause());
            }
        });
    }

    /**
     * 用户扫描二维码关注处理
     * @param encryptMsg
     * @param wxPublic
     * @return
     * @throws Exception
     */
    public Future<Void> handleQrSubcribeMsg(WXEncryptMsg encryptMsg, WXPublic wxPublic) throws Exception {
        Future<Void> qrSubscribeMsgHandlerFut = Future.future();
        WXQRCodeMsg wxqrCodeMsg = (WXQRCodeMsg)XMLUtils.mapToBean(encryptMsg.getElementMap(), WXQRCodeMsg.class);
        // 解析客服信息
        String qrParam = wxqrCodeMsg.getEventKey();
        LOGGER.info("扫码的参数：" + qrParam);
        String[] params;
        // qrscene_csId,userId,nickname
        qrParam = qrParam.substring(8);
        params = qrParam.split(",");
        String csId = params[0];
        String talkId = params[1];
        String nickname = params[2];

        // 添加用户，绑定客服
        // httpService.getWXUserInfoFromWX(wxqrCodeMsg.getFromUserName(), wxPublic.getAccessToken())
        webClientService.getWXUserInfoFromWX(wxqrCodeMsg.getFromUserName(), wxPublic.getAccessToken())
                .compose(wxUser -> {
                    // 映射客服
                    AppUser appUser = new AppUser();

                    appUser.setOpenId(wxUser.getWxOpenId())
                            .setCsId(csId)
                            .setNickname(nickname)
                            .setTalkId(talkId)
                            .setStatus("0")
                            .setChannelType(ChannelType.WX.val())
                            .setCreateTime(PeriodicTime.getCurrentTimeByyyyMMddHHmmss());
                    // 保存用户映射信息
                    Future<AppUser> appUserFut = dataAccess.saveAppUser(appUser);
                    // 发送新用户通知app后台
                    Future<Void> sendUser = webClientService.sendNewWXUserToAppBack(wxUser, appUser.getCsId(), ChannelType.WX.val());

                    //**************************************************
                    // 获取微官网信息
                    /**
                     * 2017-7-17
                     * 暂时不用微官网
                     */
//                    Future<JsonObject> microwebsiteFut = webClientService.getMicroWebSiteInfo(wxqrCodeMsg.getToUserName(), csId);
                    //**************************************************

                    // 保存微信用户信息
                    Future<WXUser> saveWxUserFut = dataAccess.insertWxUser(wxUser);
                    // 生成用户互动限制
                    UserLimit newUserLimit = new UserLimit();
                    newUserLimit.setDeveloperId(wxqrCodeMsg.getToUserName())
                            .setOpenId(wxqrCodeMsg.getFromUserName())
                            .setLaseSendTimestamp(PeriodicTime.getCurrentTimestamp())
                            .setPicLimit(ActiveLimit.PIC_LIMIT.getLimit())
                            .setTextLimit(ActiveLimit.TEXT_LIMIT.getLimit())
                            .setPicTextLimt(ActiveLimit.PIC_TEXT_LIMIT.getLimit());
                    // 先查找用户之前最后一次聊天，再将其赋值给LatelyMsgId
                    Future<UserLimit> saveUserLimit = sqlService.findLastSendMsgByOpenId(wxqrCodeMsg.getFromUserName())
                            .compose(lastMsg -> {
                                newUserLimit.setLatelyMsgId(lastMsg.getId()==null?0L:lastMsg.getId());
                                return sqlService.save(newUserLimit);
                            });
                    // 生成客服服务数据记录
                    CusServiceData cusServiceData = new CusServiceData();
                    cusServiceData.setActiveCount(0L)
                            .setCsId(appUser.getCsId())
                            .setCreateTime(PeriodicTime.getCurrentTimestamp())
                            .setCurrentUse("0")
                            .setDeveloperId(wxqrCodeMsg.getToUserName())
                            .setOpenId(wxqrCodeMsg.getFromUserName());
                    Future<CusServiceData> saveCusServDateFut = sqlService.save(cusServiceData);
                    //客服问候语
                	Future<String> greetingFut = webClientService.getCSGreetingByCsId(Integer.parseInt(csId));
                    //                                        microwebsiteFut
                    return CompositeFuture.all(appUserFut, sendUser, saveUserLimit, saveCusServDateFut, saveWxUserFut, greetingFut);
                })
                //**************************************************
                /**
                 * TODO 去掉微官网信息
                 * 2017-7-17
                 */
//                .compose( compositeFutureAsyncResult -> {
//                    JsonObject micorInfo = compositeFutureAsyncResult.result().resultAt(2);
//                    LOGGER.info("micorInfo->" + micorInfo);
//                    if (micorInfo == null) {
//                        return Future.succeededFuture();
//                    }
//                    // 返回图文消息
//                    JsonObject replyJson = WXArticleReply.buildJsonReply(wxqrCodeMsg.getFromUserName(), micorInfo);
//                    return webClientService.postReplyMsg2WxUser(replyJson, wxPublic.getAccessToken());
//                })
                //**************************************************
                .compose(picTextAr -> {
                    // 记录用户扫码关注
                    UserAction userAction = new UserAction();
                    userAction.setOpenId(wxqrCodeMsg.getFromUserName())
                            .setActStartTime(PeriodicTime.getCurrentTimestamp())
                            .setActType("QR_SUBSCRIBE")
                            .setDescription("扫码关注了公众号");
                    Future<UserAction> saveFut = sqlService.save(userAction);
                    // 返回文字问候
//                    StringBuilder textContentSb = new StringBuilder()
//                            .append("Hello，您好呀！终于等到您啦，").append("\n")
//                            .append("我是[" + wxPublic.getName() + "]客服").append(StringUtils.isBlank(nickname)?"":"["+nickname+"]").append("\n")
//                            .append("正心念着等您的召唤呢~~").append("\n")
//                            .append("看我美照请回复【1】").append("\n")
//                            .append("找我唠嗑请回复【2】").append("\n")
//                            .append("遇到问题请直接留言，我会马上回复你哦！");

                    String greetingWords = picTextAr.resultAt(5).toString();
                    
                    JsonObject replyJson = WxUtils.buildReplyJsonMsg(wxqrCodeMsg.getFromUserName(), "text", greetingWords);
                    LOGGER.info(">>>replyJsonStr-> {}", replyJson.encodePrettily());
                    Future<String> replyFut = webClientService.postReplyMsg2WxUser(replyJson , wxPublic.getAccessToken());
                    return CompositeFuture.all(saveFut, replyFut);
                })
                .compose(saveAndReplyAr -> {
                    qrSubscribeMsgHandlerFut.complete();
                }, qrSubscribeMsgHandlerFut);
        return qrSubscribeMsgHandlerFut;
    }

    /**
     * 用户搜索关注处理
     * @param encryptMsg
     * @param wxPublic
     * @return
     * @throws Exception
     */
    public Future<Void> handleSubcribeMsg(WXEncryptMsg encryptMsg, WXPublic wxPublic) throws Exception {
        Future<Void> subscribeMsgHandlerFut = Future.future();
        WXSubcribe wxSubcribeMsg = (WXSubcribe) XMLUtils.mapToBean(encryptMsg.getElementMap(), WXSubcribe.class);
        saveWxUser(wxSubcribeMsg, wxPublic, true).setHandler(subscribeMsgHandlerFut.completer());
        return subscribeMsgHandlerFut;
    }

    /**
     * 分离出来的：保存新微信用户流程
     * @param wxSubcribeMsg
     * @param wxPublic
     * @return
     */
    public Future<Void> saveWxUser(WXSubcribe wxSubcribeMsg, WXPublic wxPublic, boolean isSubscribe) {
        Future<Void> saveUserFut = Future.future();
        // 查询微信用户信息
        Future<WXUser> wxUserFuture = webClientService.getWXUserInfoFromWX(wxSubcribeMsg.getFromUserName(), wxPublic.getAccessToken());
        // 查找一个会话映射(openid - userid)
        Future<AppUser> csUserFuture = webClientService.getCustomerServerByOpenidFromAppBack(wxSubcribeMsg.getFromUserName(), wxSubcribeMsg.getToUserName(), ChannelType.WX.val());
        // 获取随机app客服
        Future<CustomerServ>  customerServImageFut = dataAccess.getCSInfoByRandom();
        CompositeFuture.all(wxUserFuture, csUserFuture, customerServImageFut)
                .compose(wxUserAndCsUserAr -> {
                    WXUser wxUser = wxUserAndCsUserAr.resultAt(0);
                    AppUser appUser = wxUserAndCsUserAr.resultAt(1);
                    appUser.setChannelType(ChannelType.WX.val());
                    CustomerServ randomCs = wxUserAndCsUserAr.resultAt(2);
                    // 保存微信用户信息
                    Future<WXUser> saveWxUserFut = Future.future();
                    if (isSubscribe) {
                        dataAccess.insertWxUser(wxUser).setHandler(saveWxUserFut.completer());
                    } else {
                        saveWxUserFut.complete();
                    }
                    // 保存用户映射
                    Future<AppUser> appUserFuture = dataAccess.saveAppUser(appUser);
                    // 通知app后台有新用户生成
                    Future<Void> sendUserFuture = webClientService.sendNewWXUserToAppBack(wxUser, appUser.getCsId(), ChannelType.WX.val());
                    //**************************************************
                    /**
                     * TODO 暂时取消微官网
                     */
                    // 获取微官网信息
//                    Future<JsonObject> microwebsiteFuture = webClientService.getMicroWebSiteInfo(wxSubcribeMsg.getToUserName(), appUser.getCsId());
                    //**************************************************
                    // 生成用户互动限制
                    UserLimit newUserLimit = new UserLimit();
                    newUserLimit.setDeveloperId(wxSubcribeMsg.getToUserName())
                            .setOpenId(wxSubcribeMsg.getFromUserName())
                            .setLaseSendTimestamp(PeriodicTime.getCurrentTimestamp())
                            .setPicLimit(ActiveLimit.PIC_LIMIT.getLimit())
                            .setTextLimit(ActiveLimit.TEXT_LIMIT.getLimit())
                            .setPicTextLimt(ActiveLimit.PIC_TEXT_LIMIT.getLimit());
                    // 先查找用户之前最后一次聊天，再将其赋值给LatelyMsgId
                    Future<UserLimit> saveUserLimit = sqlService.findLastSendMsgByOpenId(wxSubcribeMsg.getFromUserName())
                            .compose(lastMsg -> {
                                newUserLimit.setLatelyMsgId(lastMsg.getId()==null?0L:lastMsg.getId());
                                return sqlService.save(newUserLimit);
                            });
                    // 生成客服服务数据记录
                    CusServiceData cusServiceData = new CusServiceData();
                    cusServiceData.setActiveCount(0L)
                            .setCsId(appUser.getCsId())
                            .setCreateTime(PeriodicTime.getCurrentTimestamp())
                            .setCurrentUse("0")
                            .setDeveloperId(wxSubcribeMsg.getToUserName())
                            .setOpenId(wxSubcribeMsg.getFromUserName());
                    Future<CusServiceData> saveCusServDateFut = sqlService.save(cusServiceData);
                    // 通知客服：有新用户关注了公众号
                    // 生成消息数据
                    ExtraMsg extraMsg = new ExtraMsg();
                    extraMsg.setDeveloperId(wxPublic.getDeveloperId())
                            .setHeadUrl(wxUser.getHeadUrl() == null ? "" : wxUser.getHeadUrl())
                            .setNickName(wxUser.getWxNickName() == null ? "" : wxUser.getWxNickName())
                            .setPublicName(wxPublic.getName() == null ? "" : wxPublic.getName())
                            .setOpenId(wxSubcribeMsg.getFromUserName())
                            .setType(SourceType.WX.val())
                            .setChannelType(ChannelType.WX.val())
                            .setTimestamp(wxSubcribeMsg.getCreateTime());
                    JsonObject msgJson = new JsonObject();
                    msgJson.put("content", "[" + wxUser.getWxNickName() + "]关注了公众号[" + wxPublic.getName() + "]")
                            .put("extra", Json.encode(extraMsg));

                    RCSingleForm singleForm = new RCSingleForm();
                    singleForm.setFromUserId(randomCs.getTalkId())// 随机的chatId
                            .setToUserId(appUser.getTalkId())
                            .setObjectName(RCMsgType.TEXT.val())
                            .setContent(msgJson.encodePrettily());
                    Future<Void> notifyCsFut = Future.future();
                    // 发送消息至融云平台
                    sendSingMsgToRC(singleForm, notifyCsFut);
                    //                                              microwebsiteFuture
                    return CompositeFuture.all(appUserFuture, sendUserFuture, saveUserLimit, saveCusServDateFut, saveWxUserFut, notifyCsFut);
                })
                .compose( compositeFutureAsyncResult -> {
                    AppUser appUser = compositeFutureAsyncResult.result().resultAt(0);
//                    JsonObject micorInfo = compositeFutureAsyncResult.result().resultAt(3);

                    // 记录用户关注
                    UserAction userAction = new UserAction();
                    userAction.setOpenId(wxSubcribeMsg.getFromUserName())
                            .setActStartTime(PeriodicTime.getCurrentTimestamp())
                            .setActType("SUBSCRIBE")
                            .setDescription("关注了公众号");
                    Future saveUserActionFut = sqlService.save(userAction);

                    //**************************************************
                    /**
                     * TODO 暂时取消微官网
                     * 2017-7-17
                     */
                    Future<String> replyPicTextFut = Future.future();
                    replyPicTextFut.complete();
//                    if (micorInfo == null) {
//                        replyPicTextFut.complete();
//                    } else {
//                        // 返回图文消息
//                        JsonObject subScribePicTextReplyJson = WXArticleReply.buildJsonReply(wxSubcribeMsg.getFromUserName(), micorInfo);
//                        replyPicTextFut = webClientService.postReplyMsg2WxUser(subScribePicTextReplyJson, wxPublic.getAccessToken());
//                    }
                    //**************************************************
                    //客服问候语
                    Future<String> greetingFut = webClientService.getCSGreetingByCsId(Integer.valueOf(appUser.getCsId()));
                    
                    CompositeFuture.all(saveUserActionFut, replyPicTextFut, greetingFut)
                            .compose(compositeFutureAr -> {
                                // 返回文字问候
                                // Hello，你好呀！终于等到您啦，我是xxx（公众号名称）客服小微xxx，正心念着等您的召唤呢~~看我美照请回复【1】，找我唠嗑请回复【2】，查看历史文章请回复【3】,遇到问题请直接留言，我会马上回复你哦!
//                                StringBuilder textContentSb = new StringBuilder()
//                                        .append("Hello，您好呀！终于等到您啦，").append("\n")
//                                        .append("我是[" + wxPublic.getName() + "]客服").append(StringUtils.isBlank(appUser.getNickname())?"":"["+appUser.getNickname()+"]").append("\n")
//                                        .append("正心念着等您的召唤呢~~").append("\n")
//                                        .append("看我美照请回复【1】").append("\n")
//                                        .append("找我唠嗑请回复【2】").append("\n")
//                                        .append("遇到问题请直接留言，我会马上回复你哦！");
                            	String greetingWords = compositeFutureAr.resultAt(2).toString();
                                JsonObject subScribeTextReply = WxUtils.buildReplyJsonMsg(wxSubcribeMsg.getFromUserName(), "text", greetingWords);
                                return webClientService.postReplyMsg2WxUser(subScribeTextReply, wxPublic.getAccessToken());
                            })
                            .compose(replyAr -> {

                                saveUserFut.complete();
                            }, saveUserFut);
                }, saveUserFut);
        return saveUserFut;
    }

    /**
     * 用户取消关注处理
     * @param encryptMsg
     * @param wxPublic
     * @return
     * @throws Exception
     */
    public Future<Void> handleUnSubcribeMsg(WXEncryptMsg encryptMsg, WXPublic wxPublic) throws Exception {
        Future<Void> unSubscribeMsgHandlerFut = Future.future();
        WXSubcribe wxSubcribe = (WXSubcribe) XMLUtils.mapToBean(encryptMsg.getElementMap(), WXSubcribe.class);
        // 删除该用户信息和映射信息
        dataAccess.deleteAppUserByOpenId(wxSubcribe.getFromUserName()).setHandler(delAs -> {
            if (delAs.failed()) {
                LOGGER.info("[删除用户信息和映射失败 error：{}]", delAs.cause().getMessage());
                unSubscribeMsgHandlerFut.fail(delAs.cause());
            } else {
                // 发送HTTP请求，通知app后台用户取消关注
                webClientService.userUnsubcribe(wxSubcribe.getFromUserName())
                        .compose((Void) -> {
                            // 删除用户限制记录
                            Future delLimitFut = sqlService.deleteUserLimit(wxSubcribe.getFromUserName());
                            // 记录用户扫码关注
                            UserAction userAction = new UserAction();
                            userAction.setOpenId(wxSubcribe.getFromUserName())
                                    .setActStartTime(PeriodicTime.getCurrentTimestamp())
                                    .setActType("UNSUBSCRIBE")
                                    .setDescription("取消关注公众号");
                            Future saveActionFut = sqlService.save(userAction);
                            return CompositeFuture.all(delLimitFut, saveActionFut);
                        })
                        .compose(compositeFutureAr -> {
                            unSubscribeMsgHandlerFut.complete();
                            LOGGER.info("[通知app后台用户取消关注]请求发送完成");
                        }, unSubscribeMsgHandlerFut);
            }
        });
        return unSubscribeMsgHandlerFut;
    }

    /**
     * 用户点击自定义菜单处理
     * @param encryptMsg
     * @param wxPublic
     * @return
     * @throws Exception
     */
    public Future<Void> handleClickMenuMsg(WXEncryptMsg encryptMsg, WXPublic wxPublic) throws Exception {
        Future clickMenuMsgHandlerFut = Future.future();
        WXCustoMenuMsg custoMenuMsg = (WXCustoMenuMsg) XMLUtils.mapToBean(encryptMsg.getElementMap(), WXCustoMenuMsg.class);

        RedirectUrl redirectUrl = new RedirectUrl();
        redirectUrl
                .setRedirectUrl("http://www.quanzilife.cn/index.php?g=Wap&m=Index&a=content&id=805&classid=685&token=hzmndo1454489422")
                .setState("STATE");

        JsonObject replyJson = null;
        if ("V1001_NEW_MESSAGE".equals(custoMenuMsg.getEventKey())) {
            WXArticleReply articleReply = new WXArticleReply();
            WXArticleReply.ArticleItem articleItem = articleReply.new ArticleItem();
            articleItem.setDescription("东莞市圈子你我电子商务有限公司是东莞的一家高新科技公司...")
                    .setTitle("全诚科技")
                    .setPicurl("http://www.qzlife.net/data/mark/logo.png")
                    .setUrl(redirectUrl.getRedirectUrl());
            JsonArray articles = new JsonArray();
            articles.add(JsonObject.mapFrom(articleItem));
            replyJson = WXArticleReply.buildJsonReply(custoMenuMsg.getFromUserName(), new JsonObject().put("articles", articles));
        } else if ("V1001_PRODUCT_CENTER".equals(custoMenuMsg.getEventKey())) {
            redirectUrl.setRedirectUrl("http://www.quanzilife.cn/index.php?g=Wap&m=Index&a=content&id=806&classid=684&token=hzmndo1454489422");
            // 返回
            StringBuilder textContentSb = new StringBuilder()
                    .append("[产品中心]\n")
                    .append("产品一：[特销产品]").append("\n").append("详情地址：" + redirectUrl.getRedirectUrl()).append("\n\n")
                    .append("产品二：[产品详情]").append("\n").append("详情地址：<a href='"+redirectUrl.getRedirectUrl()+"'>详情</a>").append("\n\n")
                    .append("产品三：[百度]").append("\n").append("详情地址：[http://www.baidu.com]");
            replyJson = WxUtils.buildReplyJsonMsg(custoMenuMsg.getFromUserName(), "text", textContentSb.toString());
        } else {

        }
        webClientService.postReplyMsg2WxUser(replyJson, wxPublic.getAccessToken())
                .compose(replyAr -> {
                    UserAction userAction = new UserAction();
                    String description = "";
                    if ("V1001_NEW_MESSAGE".equals(custoMenuMsg.getEventKey())) {
                        description = "[最新消息]";
                    } else if ("V1001_PRODUCT_CENTER".equals(custoMenuMsg.getEventKey())) {
                        description = "[产品中心]";
                    }

                    // 更新互动时间
                    Future updateUserLimitFut = sqlService.updateUserLimit(custoMenuMsg.getFromUserName(),
                            new EnumMap<ActiveLimit, Long>(ActiveLimit.class) {
                                {
                                    put(ActiveLimit.ACTIVE_LIMIT, PeriodicTime.getCurrentTimestamp());
                                }
                            });
                    // 保存用户行为
                    userAction.setOpenId(custoMenuMsg.getFromUserName())
                            .setActStartTime(PeriodicTime.getCurrentTimestamp())
                            .setActType(ActionType.CUSTOMIZE_MENU_CLICK.getName())
                            .setDescription(ActionType.CUSTOMIZE_MENU_CLICK.getDescription() + description);
                    Future saveUserActionFut = sqlService.save(userAction);

                    // 上传自定义菜单轨迹
                    Future uploadMenuTrackFut = Future.future();
                    sqlService.getWxUserByOpenId(custoMenuMsg.getFromUserName())
                            .compose(wxUser -> {
                                UserActionDTO userActionDTO = new UserActionDTO();
                                userActionDTO.setDeveloperId(wxPublic.getDeveloperId())
                                        .setPubname(wxPublic.getName())
                                        .setActionTime(PeriodicTime.getCurrentTimestamp())
                                        .setHeadUrl(wxUser.getHeadUrl())
                                        .setNickname(wxUser.getWxNickName())
                                        .setActionUrl("自定义菜单键[" + custoMenuMsg.getEventKey() + "]")
                                        .setDescription(userAction.getDescription())
                                        .setOpenId(wxUser.getWxOpenId());
                                webClientService.postUserTrackToAppBack(userActionDTO.toJson())
                                        .setHandler(uploadMenuTrackFut.completer());
                            }, uploadMenuTrackFut);
                    CompositeFuture.all(updateUserLimitFut, saveUserActionFut, uploadMenuTrackFut)
                            .setHandler(clickMenuMsgHandlerFut.completer());
                }, clickMenuMsgHandlerFut);
        return clickMenuMsgHandlerFut;
    }

    /**
     * 用户点击自定义菜单跳转页面处理
     * @param encryptMsg
     * @param wxPublic
     * @return
     * @throws Exception
     */
    public Future<Void> handleViewMenuMsg(WXEncryptMsg encryptMsg, WXPublic wxPublic) throws Exception {
        Future viewMenuMsgHandlerFut = Future.future();
        WXCustoMenuMsg custoMenuMsg = (WXCustoMenuMsg) XMLUtils.mapToBean(encryptMsg.getElementMap(), WXCustoMenuMsg.class);

        blockingService.getTitleFromURL(custoMenuMsg.getEventKey())
                .compose(title -> {
                    UserAction userAction = new UserAction();
                    // 查找url中的标题
                    String description = "[" + title.trim() + "]";
                    userAction.setOpenId(custoMenuMsg.getFromUserName())
                            .setActStartTime(PeriodicTime.getCurrentTimestamp())
                            .setActType(ActionType.CUSTOMIZE_MENU_VIEW.getName())
                            .setDescription(ActionType.CUSTOMIZE_MENU_VIEW.getDescription() + description);
                    // 更新互动时间
                    Future updateUserLimitFut = sqlService.updateUserLimit(custoMenuMsg.getFromUserName(),
                            new EnumMap<ActiveLimit, Long>(ActiveLimit.class) {
                                {
                                    put(ActiveLimit.ACTIVE_LIMIT, PeriodicTime.getCurrentTimestamp());
                                }
                            });
                    // 上传自定义菜单轨迹
                    Future uploadUserActionFut = Future.future();
                    sqlService.getWxUserByOpenId(custoMenuMsg.getFromUserName())
                            .compose(wxUser -> {
                                UserActionDTO userActionDTO = new UserActionDTO();
                                userActionDTO.setDeveloperId(wxPublic.getDeveloperId())
                                        .setPubname(wxPublic.getName())
                                        .setActionTime(PeriodicTime.getCurrentTimestamp())
                                        .setHeadUrl(wxUser.getHeadUrl())
                                        .setNickname(wxUser.getWxNickName())
                                        .setActionUrl(custoMenuMsg.getEventKey())
                                        .setDescription(userAction.getDescription())
                                        .setOpenId(wxUser.getWxOpenId());
                                webClientService.postUserTrackToAppBack(userActionDTO.toJson())
                                        .setHandler(uploadUserActionFut.completer());
                            }, uploadUserActionFut);

                    // 保存用户行为
                    Future saveActionFut = sqlService.save(userAction);
                    CompositeFuture.all(updateUserLimitFut, uploadUserActionFut, saveActionFut)
                            .setHandler(viewMenuMsgHandlerFut.completer());
                }, viewMenuMsgHandlerFut);
        return viewMenuMsgHandlerFut;
    }

    /**
     * 一键拉取历史粉丝处理（成为预备粉丝）
     * @param
     */
    @RouteMapping(path = "/:developerid/history/fans")
    @MethodLog("generate history fans by developerid: wx operation")
    public void generateHistoryFansByDeveloperIdForTest(RoutingContext context) { //String developerId
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();
        String developerId = request.getParam("developerid");
        if (StringUtils.isEmpty(developerId)) {
            HttpUtils.commonJsonResponse(response, SendResult.FAILED_PARAM.getResultJson("developerid"));
        }
        Future fansListFut = Future.future();
        fansListFut = generateHistoryFansByDeveloperId(developerId);
        // 通用future结束处理
        commonEndFuture(fansListFut, "generate history fans by developerid: wx operation", LOGGER);
    }

    /**
     * 微信URL认证GET
     * @param context
     */
    @RouteMapping(path = "/message")
    @MethodLog("微信URL认证")
    public void authenticateURL(RoutingContext context) {
        HttpServerRequest request = context.request();
        HttpServerResponse response = context.response();

        // 需要被回写的随机字符串
        String echostr = request.getParam("echostr");
        if (WxUtils.checkRequest(request)) {
            response.end(echostr);
            LOGGER.info("--微信URL认证完成--");
        } else {

            LOGGER.info("--微信URL认证失败--");
        }
    }

    // 获取APP用户对应的talkId(如若不存在则去app后台获取、绑定)
    public Future<AppUser> getMappedAppUserInfo(String openId, String developerId) {
        Future<AppUser> appUserTalkIdFut = Future.future();
        dataAccess.getAppUser(openId)
                .setHandler(appUserInfoAs -> {
                    if (appUserInfoAs.failed()) {
                        // 不存在则查询是否是历史粉丝，历史粉丝则新增用户
                        dataAccess.getWXPublicInfoByDeveloperId(developerId)
                                .compose(wxPublic -> {
                                    // 获取公众号下粉丝列表
                                    Future<List<String>> openIdListFuture = Future.future();
                                    List<String> openInList = new ArrayList<>();
                                    webClientService.getOpenIdListByLoop(wxPublic.getAccessToken(), null, openIdListFuture, openInList);
                                    //
                                    openIdListFuture
                                            .compose(allFansListAr -> {
                                                List<String> allFansList = allFansListAr;
                                                boolean exist = allFansList.stream().anyMatch(fansOpenId -> fansOpenId.equals(openId));
                                                if (exist) {
                                                    // 查询用户信息
                                                    webClientService.getWXUserInfoFromWX(openId, wxPublic.getAccessToken())
                                                            .compose(wxUser -> {
                                                                // 生成虚拟用户关注
                                                                WXSubcribe wxSubcribeMsg = new WXSubcribe();
                                                                wxSubcribeMsg.setFromUserName(openId)
                                                                        .setToUserName(wxPublic.getDeveloperId())
                                                                        .setCreateTime(PeriodicTime.getCurrentTimeByyyyMMddHHmmss());
                                                                // 走用户搜索关注处理流程
                                                                saveWxUser(wxSubcribeMsg, wxPublic, false)
                                                                        .compose(saved ->
                                                                                getMappedAppUserInfo(openId, developerId)
                                                                        )
                                                                        .setHandler(appUserTalkIdFut.completer());
                                                            }, appUserTalkIdFut);
                                                } else {
                                                    appUserTalkIdFut.fail("不存在该用户对应的客服");
                                                }
                                            }, appUserTalkIdFut);
                                }, appUserTalkIdFut);
                    } else {
                        appUserTalkIdFut.complete(appUserInfoAs.result());
                    }
                });
        return appUserTalkIdFut;
    }

    /**
     * 消息类型枚举
     */
    enum RCMsgType {
        TEXT("RC:TxtMsg"),
        IMAGE("RC:ImgMsg");

        private String name;
        RCMsgType(String name) {
            this.name = name;
        }

        public String val() {
            return this.name;
        }
    }

    public static void main(String[] args) {

    }
}
